// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

using System;
using System.Security.Cryptography;
using System.ServiceModel;
using System.Xml.Linq;

namespace SL.IdentityModel.Protocols.WSTrust
{
    /// <summary>
    /// This class contains infromation about the WS-Trust RequestSecurityTokenResponse
    /// </summary>
    public class RequestSecurityTokenResponse
    {
        private RequestedSecurityToken _requestedSecurityToken;
        private EndpointAddress _appliesTo = null;
        private string _computedKeyAlgorithm = null;
        private DateTime _created;
        private byte[] _entropyBytes = null;
        private DateTime _expires;
        private int? _keySizeInBits = null;
        private string _keyType = null;
        private XNode _proofTokenReference = null;
        private byte[] _protectedKeyBytes = null;
        private RequestSecurityToken _rst;
        private byte[] _symmetricProofTokenBytes = null;
        private bool _symmetricProofTokenComputed = false;
        private object _symmetricProofTokenSyncRoot = new object();

        /// <summary>
        /// the requested security token
        /// </summary>
        public RequestedSecurityToken RequestedSecurityToken
        {
            get { return _requestedSecurityToken; }
            set { _requestedSecurityToken = value; }
        }

        /// <summary>
        /// AppliesTo infomation from the response
        /// </summary>
        public EndpointAddress AppliesTo
        {
            get { return _appliesTo; }
            set { _appliesTo = value; }
        }

        /// <summary>
        /// Combined entropy for client/ server
        /// </summary>
        public byte[] SymmetricProofTokenBytes
        {
            get
            {
                if ( false == _symmetricProofTokenComputed )
                {
                    lock ( _symmetricProofTokenSyncRoot )
                    {
                        if ( false == _symmetricProofTokenComputed )
                        {
                            #region Server provided the key --> User it!
                            if ( null != ProtectedKeyBytes )
                            {
                                _symmetricProofTokenBytes = (byte[]) ProtectedKeyBytes.Clone();
                            }
                            #endregion

                            #region Server provided entropy and/ or computation algorithm --> Combine with client entropy
                            else if ( !String.IsNullOrEmpty( ComputedKeyAlgorithm ) || EntropyBytes != null )
                            {
                                if ( null == EntropyBytes )
                                {
                                    throw new InvalidOperationException( "Server failed to provide entropy together with key computation algorithm." );
                                }

                                if ( String.IsNullOrEmpty( ComputedKeyAlgorithm ) )
                                {
                                    throw new InvalidOperationException( "Server failed to provide key computation algorithm alongside entropy." );
                                }

                                if ( !String.Equals( ComputedKeyAlgorithm, WSTrust13Constants.ComputedKeyAlgorithms.PSHA1 ) )
                                {
                                    throw new InvalidOperationException(
                                        String.Format( "Unknown key computation algorithm: '{0}'. Expected: '{1}'.",
                                            ComputedKeyAlgorithm,
                                            WSTrust13Constants.ComputedKeyAlgorithms.PSHA1 ) );
                                }

                                if ( null == RST )
                                {
                                    throw new InvalidOperationException( "RST is required to compute the symmetric proof token." );
                                }

                                if ( null == RST.EntropyBytes )
                                {
                                    throw new InvalidOperationException( "RST must contain entropy when server returns a key computation algorithm." );
                                }

                                int keySize = RequestSecurityToken.SymmetricKeySizeInBitsConstant;
                                if ( null != KeySizeInBits )
                                {
                                    keySize = KeySizeInBits.Value;
                                }
                                else if ( null != RST.KeySizeInBits )
                                {
                                    keySize = RST.KeySizeInBits.Value;
                                }

                                _symmetricProofTokenBytes = CalculatePSHA1( RST.EntropyBytes, EntropyBytes, keySize );
                            }
                            #endregion

                            #region Client provided entropy
                            else if ( null != RST && null != RST.EntropyBytes )
                            {
                                _symmetricProofTokenBytes = RST.EntropyBytes;
                            }
                            #endregion

                            #region No proof key
                            else
                            {
                                _symmetricProofTokenBytes = null;
                            }
                            #endregion

                            _symmetricProofTokenComputed = true;
                        }
                    }
                }

                return _symmetricProofTokenBytes;
            }
            set { _symmetricProofTokenBytes = value; _symmetricProofTokenComputed = true; }
        }

        /// <summary>
        /// Algorithm that should be used to combina client/ server entropy
        /// </summary>
        public string ComputedKeyAlgorithm
        {
            get { return _computedKeyAlgorithm; }
            set { _computedKeyAlgorithm = value; }
        }

        /// <summary>
        /// Response created time
        /// </summary>
        public DateTime Created
        {
            get { return _created; }
            set { _created = value; }
        }

        /// <summary>
        /// Entropy returned by the server
        /// </summary>
        public byte[] EntropyBytes
        {
            get { return _entropyBytes; }
            set { _entropyBytes = value; }
        }

        /// <summary>
        /// Response expires time
        /// </summary>
        public DateTime Expires
        {
            get { return _expires; }
            set { _expires = value; }
        }

        /// <summary>
        /// KeySize (in bits) for the proof token
        /// </summary>
        public int? KeySizeInBits
        {
            get { return _keySizeInBits; }
            set { _keySizeInBits = value; }
        }

        /// <summary>
        /// Requested proof key type
        /// </summary>
        public string KeyType
        {
            get { return _keyType; }
            set { _keyType = value; }
        }

        public XNode ProofTokenReference
        {
            get { return _proofTokenReference; }
            set { _proofTokenReference = value; }
        }

        public byte[] ProtectedKeyBytes
        {
            get { return _protectedKeyBytes; }
            set { _protectedKeyBytes = value; }
        }

        /// <summary>
        /// The RST used to retrieve this RSTR
        /// </summary>
        internal RequestSecurityToken RST
        {
            get { return _rst; }
            set { _rst = value; }
        }

        /// <summary>
        /// Calculate key calculated key entropy using PHSA1
        /// </summary>
        /// <param name="client"></param>
        /// <param name="serverserverEntropy"></param>
        /// <param name="sizeBits"></param>
        /// <returns></returns>
        public static byte[] CalculatePSHA1( byte[] client, byte[] server, int sizeBits )
        {
            int sizeBytes = sizeBits / 8;

            using ( HMAC hash = new HMACSHA1() )
            {
                hash.Key = client;
                int bufferSize = hash.HashSize / 8 + server.Length;
                int i = 0;

                byte[] b1 = server;
                byte[] b2 = new byte[bufferSize];
                byte[] temp = null;
                byte[] psha = new byte[sizeBytes];


                while ( i < sizeBytes )
                {
                    hash.Initialize();
                    b1 = hash.ComputeHash( b1 );
                    b1.CopyTo( b2, 0 );
                    server.CopyTo( b2, hash.HashSize / 8 );

                    temp = hash.ComputeHash( b2 );

                    for ( int j = 0; j < temp.Length; j++ )
                    {
                        if ( i < sizeBytes )
                        {
                            psha[i] = temp[j];
                            i++;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                return psha;
            }
        }


    }
}